package com.xjuse.yida.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xjuse.yida.commom.ConstantEnum;
import com.xjuse.yida.commom.redis.RedissonUtil;
import com.xjuse.yida.commom.util.Result;
import com.xjuse.yida.commom.util.TimeUtil;
import com.xjuse.yida.dto.UserClothRecomDto;
import com.xjuse.yida.dto.WearRecomHistoryDto;
import com.xjuse.yida.entity.Cloth;
import com.xjuse.yida.entity.LocationWeather;
import com.xjuse.yida.entity.UserClothes;
import com.xjuse.yida.entity.WearRecommand;
import com.xjuse.yida.mapper.WearRecommandMapper;
import com.xjuse.yida.service.ClothesService;
import com.xjuse.yida.service.LocationWeatherService;
import com.xjuse.yida.service.UserClothesService;
import com.xjuse.yida.service.WearRecommandService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户衣物穿搭推荐 服务实现类
 * </p>
 *
 * @author duwx
 * @since 2022-10-21
 */
@Service
@Slf4j
public class WearRecommandServiceImpl extends ServiceImpl<WearRecommandMapper, WearRecommand> implements WearRecommandService {

    @Autowired
    private UserClothesService userClothesService;
    @Autowired
    private LocationWeatherService weatherService;
    @Autowired
    private ClothesService clothesService;
    @Autowired
    private WearRecommandMapper wearRecommandMapper;
    @Autowired
    private RedissonUtil redissonUtil;
    @Autowired
    private RedissonClient redissonClient;

    public Result createUserClothRecommend(String userId){
        //获取当天的天气信息
        LocationWeather locationWeather = weatherService.findUserCurrLocationWeather(userId);
        //获取用户衣物 穿搭分类信息
        Map<String, List<UserClothes>> map = userClothesService.clothByRecomType(userId);
        UserClothRecomDto recomDto = new UserClothRecomDto();
        AtomicInteger flag = new AtomicInteger();
        if (map!=null&&map.size()==4){
            map.forEach((key, userCloths) -> {
                //1.季节筛选
                userCloths = seasonFilter(userCloths);
                //2.温度匹配
                UserClothes clothes = temperatureFilter(locationWeather, userCloths);
                //组装最终的推荐数据
                if (clothes==null){
                    flag.getAndIncrement();
                }
                recomDto.getData().put(key,clothes);
            });
        }
        if (flag.get()>=2){
            return Result.failed("您当季衣物过少，不能生成有效搭配，请添加衣物后重试！");
        }
        recomDto.setTime(LocalDateTime.now());
        //记录穿搭历史
        recordRecomHistory(recomDto);
        return Result.ok(recomDto);
    }

    @Async
    public void recordRecomHistory(UserClothRecomDto recomDto) {
        Map<String, UserClothes> data = recomDto.getData();
        LocalDateTime time = recomDto.getTime();
        List<WearRecommand> wearRecommands = new ArrayList<>();
        String wearRecomId = UUID.fastUUID().toString(true);
        for (Map.Entry<String, UserClothes> entry : data.entrySet()) {
            if (entry.getValue()!=null){
                UserClothes userClothes = entry.getValue();
                WearRecommand recommand = WearRecommand.builder()
                        .clothId(userClothes.getClothId())
                        .clothType(userClothes.getClothType())
                        .recomId(wearRecomId)
                        .userId(userClothes.getUserId())
                        .userName(userClothes.getUserName())
                        .recomTime(time)
                        .build();
                wearRecommands.add(recommand);
            }
        }
        boolean b = this.saveBatch(wearRecommands);
        updateUserClothesWearFreq(wearRecommands);
        Assert.isTrue(b,String.format("穿搭推荐数据记录异常，推荐信息：%s",wearRecommands.toString()));
    }

    private UserClothes temperatureFilter(LocationWeather locationWeather, List<UserClothes> userCloths) {
        Integer currTemperature = Integer.valueOf(locationWeather.getWeathTemperature());
        //衣物温暖度与当前温度差值 越小，越合适
        return userCloths.stream().min(new Comparator<UserClothes>() {
            @Override
            public int compare(UserClothes o1, UserClothes o2) {
                return Math.abs(o1.getClothWarm() - currTemperature) - Math.abs(o2.getClothWarm() - currTemperature);
            }
        }).orElse(userCloths.size()==0?null:userCloths.get(0));
    }

    @NotNull
    private List<UserClothes> seasonFilter(List<UserClothes> userCloths) {
        String currSeason = TimeUtil.getCurrSeason();
        List<String> list = clothesService.queryClothesList().stream()
                .filter(cloth -> cloth.getClothSeason().contains(currSeason))
                .map(Cloth::getClothName).collect(Collectors.toList());
        userCloths = userCloths.stream().filter(cloth -> list.contains(cloth.getClothType())).collect(Collectors.toList());
        return userCloths;
    }

    /**
     * 用户生成穿搭信息后  更新用户当前衣物的穿搭次数
     * (用以判断是否需要对衣物进行清洁提醒)
     * @param wearRecommands 用户穿搭推荐数据
     */
    private void updateUserClothesWearFreq(List<WearRecommand> wearRecommands) {
        if (!ObjectUtils.isEmpty(wearRecommands)){
            WearRecommand first = wearRecommands.get(0);
            //存储Key前缀
            String keyPrefix = ConstantEnum.USER_CLOTHES_FREQ_KEY_PREFIX.getData()+":"+first.getUserId();
            for (WearRecommand recommand : wearRecommands) {
                Map<String, String> map = redissonClient.getMap(keyPrefix);
                String clothId = recommand.getClothId();
                if (map.containsKey(clothId)){
                    redissonClient.getMap(keyPrefix).put(clothId, Integer.parseInt(map.get(clothId))+1+"");
                }else {
                    //当前clothId不存在时，创建
                    redissonClient.getMap(keyPrefix).put(clothId, "1");
                }
            }
        }
    }

    @Override
    public List<WearRecommand> getUserClothRecomHistory(String userId) {
        QueryWrapper<WearRecommand> wrapper = new QueryWrapper<>();
        wrapper = wrapper.eq("user_id", userId).orderByDesc("recom_time");
        List<WearRecommand> recommands = wearRecommandMapper.selectList(wrapper);

        HashMap<String, WearRecomHistoryDto> map = new HashMap<>();
        //组装用户穿搭历史数据并返回
        for (WearRecommand recommend : recommands) {
            String recomId = recommend.getRecomId();
            if (!map.containsKey(recomId)) {
                WearRecomHistoryDto recomHistoryDto = WearRecomHistoryDto.builder()
                        .recomTime(recommend.getRecomTime()).recomId(recommend.getRecomId())
                        .data(new ArrayList<>(4)).build();
                recomHistoryDto.getData().add(recommend);
                map.put(recomId,recomHistoryDto);
            }else {
                map.get(recomId).getData().add(recommend);
            }
        }
        return new ArrayList(map.values());
    }

    @Override
    public boolean setUserCleanFreq(String userId,int freq) {
        String userCleanFreqKey = ConstantEnum.USER_CLEAN_FREQ_KEY_PREFIX.getData()+":"+userId;
        try {
            redissonUtil.setString(userCleanFreqKey,String.valueOf(freq));
        } catch (Exception e) {
           log.error("用户衣物清洁提醒频率设置失败：key={},error={}",userCleanFreqKey,e);
           return false;
        }
        return true;
    }

    @Override
    public List<UserClothes> needCleanUserClothesList(String userId) {
        String mapKey = ConstantEnum.USER_CLOTHES_FREQ_KEY_PREFIX.getData()+":"+userId;
        RMap<Object, Object> map = redissonClient.getMap(mapKey);
        int userFreq = gainUserCleanFreq(userId);
        ArrayList<String> temp = new ArrayList<>();
        //遍历获取需要进行清洁的衣物
        map.forEach((key, value) -> {
            if (Integer.parseInt((String) value)>=userFreq){
                temp.add((String) key);
            }
        });
        String needCleanStr = StringUtils.join(temp, ",");
        String currentDate = TimeUtil.currentDateString();
        //缓存当天需要清洁的衣物   如有
        if (needCleanStr.length()>0){
            String userCleanSetKeyPrefix = ConstantEnum.USER_CLEAN_CLOTH_SET.getData()+":"+userId;
            redissonClient.getMap(userCleanSetKeyPrefix).put(currentDate,needCleanStr);
        }
        List<UserClothes> userClothes = userClothesService.clothesByUserAndClothIds(userId, needCleanStr);
        return userClothes;
    }

    /**
     * 获取用户配置的清洁频率
     */
    private int gainUserCleanFreq(String userId) {
        String userCleanFreqKey = ConstantEnum.USER_CLEAN_FREQ_KEY_PREFIX.getData()+":"+userId;
        String userFreq = (String) redissonClient.getBucket(userCleanFreqKey).get();
        return Integer.parseInt(userFreq);
    }

    @Override
    public Map<String,List<UserClothes>> userClothesCleanHistory(String userId) {
        String userCleanSetKeyPrefix = ConstantEnum.USER_CLEAN_CLOTH_SET.getData()+":"+userId;
        RMap<String, Object> map = redissonClient.getMap(userCleanSetKeyPrefix);
        HashMap<String,List<UserClothes>> newMap = new HashMap<>();
        map.forEach((key, value) -> {
            List<UserClothes> userClothes = userClothesService.clothesByUserAndClothIds(userId, (String) value);
            newMap.put(key,userClothes);
        });
        return newMap;
    }

    @Override
    public List<UserClothes> queryUserTodayClean(String userId) {
        Map<String,List<UserClothes>> map = userClothesCleanHistory(userId);
        String currentDate = TimeUtil.currentDateString();
        if (map.containsKey(currentDate)){
            return map.get(currentDate);
        }
        return new ArrayList<>();
    }
}
